package org.moonlight.jvm.instructions.control.switchcase;

import org.moonlight.jvm.instructions.base.BranchInstruction;
import org.moonlight.jvm.instructions.base.BytecodeReader;
import org.moonlight.jvm.rtda.exclusive.Frame;

/**
 * Java语言中的switch-case语句有两种实现方式：如果case值可以编码成一个索引表，则实现成 table switch 指令；否则实现成 lookup switch 指令。
 * 下面这个 switch 可以编码成 table switch 指令
 *   switch (i) {
 *     case 0: return 0;
 *     case 1: return 1;
 *     case 2: return 2;
 *     default: return -1;
 *   }
 * 下面这个 switch 则会编码成 lookup switch 指令
 *   switch (i) {
 *     case -100: return -1;
 *     case 0: return 0;
 *     case 100: return 1;
 *     default: return -1;
 *   }
 *
 * tableSwitch: 1. 先从操作数栈中弹出一个int变量
 *              2. 然后看它是否在 low 和 high 给定的范围之内
 *              3. 如果在，则从 jumpOffsets 表中查出偏移量进行跳转
 *              4. 否则按照 defaultOffset 跳转
 *
 * @author Moonlight
 * @createTime 2023/8/23 8:58
 **/
public class TableSwitch extends BranchInstruction {

    /** default 语句跳转偏移量 **/
    private int defaultOffset;
    /** case 的取值范围 - 低位 **/
    private int low;
    /** case 的取值范围 - 高位 **/
    private int high;
    /** 各种 case 的索引表 - 存放有 high - low + 1 个 int 值，对应各种 case 的跳转偏移量 **/
    private int[] jumpOffsets;

    @Override
    public void fetchOperands(BytecodeReader reader) {
        // tableswitch指令操作码的后面有0～3字节的padding，以保证defaultOffset在字节码中的地址是4的倍数
        reader.skipPadding();

        this.defaultOffset = reader.readInt32();
        this.low = reader.readInt32();
        this.high = reader.readInt32();

        int jumpOffsetLength = this.high - this.low + 1;
        this.jumpOffsets = reader.readInt32s(jumpOffsetLength);
    }

    @Override
    public void execute(Frame frame) {
        // 先从操作数栈中弹出一个int变量
        // 然后看它是否在low和high给定的范围之内
        // 如果在，则从jumpOffsets表中查出偏移量进行跳转
        // 否则按照defaultOffset跳转
        int idx = frame.getOperandStack().popInt();
        int offset = this.defaultOffset;
        if (idx >= this.low && idx <= this.high) {
            offset = this.jumpOffsets[idx - this.low];
        }
        branch(frame, offset);
    }

}
